Imports System.Net
Imports System.Net.Sockets
Imports System.Text.Encoding
Imports System.Threading

Namespace SocketServer

    Public Module mSocketServer

        Public Class Connection

            Public WSocket As System.Net.Sockets.Socket = Nothing
            Public Buffer() As Byte
            Public BufferSize As Integer = 10240

            Public objSync As Object = New Object

            Public Tag As String
            Public Index As Integer
            Public CanLogin As Boolean = False
            Public ConnectionTime As String
            Public IP As String
            Public Crypto As Object
            Public ClientVerOK As Boolean

            Public LastRecievedDataAt As DateTime

            Public LoginID As Integer
            Public Username As String
            Public CharacterID As Integer
            Public PlayerInfo As PlayerInfo
            Public PlayerWeight As PlayerWeights
            Public PlayerJob As CharacterJobs
            Public PositionInfo As PlayerPositionInfo
            Public IsAlive As Boolean
            Public AttackMode As AttackModes

            Public VitalPoints As Vitals
            Public RegenStats As HPMPRegenStats

            Public Structure HPMPRegenStats
                Dim INT8_HPAMMOUNT As Int64
                Dim INT8_MPAMMOUNT As Int64
                Dim INT8_HPSPEED As Int64
                Dim INT8_MPSPEED As Int64
                Dim INT8_HPTICK As Int64
                Dim INT8_MPTICK As Int64
            End Structure

            Public EquipedItems() As ItemSlotInfo
            Public Stats As StatsSet

            Public IsGM As Boolean
            Public GodMode As Boolean

            Public Gold As Long
            Public GameGold As Long
            Public FamePoints As Long

            Public Level As Integer

            Public Exp As Long
            Public ExpThisLvl As Long

            Public GroupID As Long

            Private ScriptVals As New Collection

            Public Function GetValue(key As String) As Object

                If ScriptVals.Contains(key) Then
                    Return ScriptVals(key)
                End If

                Return Nothing

            End Function

            Public Sub SetValue(key As String, value As Object)

                If ScriptVals.Contains(key) Then
                    ScriptVals.Remove(key)
                End If

                ScriptVals.Add(value, key)

            End Sub

        End Class

        Public Class SocketServer

            Public Event onAccept(ByRef Conn As Connection)
            Public Event onDataArrival(ByVal Data() As Byte, ByRef Conn As Connection)
            Public Event onSend(ByRef Conn As Connection, ByVal Data As String)
            Public Event onError(ByVal ErrorType As String, ByVal Err As Exception, ByVal Conn As Connection)
            Public Event onDisconnect(ByVal Conn As Connection, ByVal IP As String)

            Public ConnectionList As New cConnectionList(Me)

            Private DataIn As Long
            Private DataOut As Long
            Private TotalDataTransfered As Long

            Public ReadOnly Property DataTransfered() As Decimal
                Get
                    Return (TotalDataTransfered / 1024)
                End Get
            End Property

            Public ReadOnly Property DataRateIn() As Single
                Get
                    Dim tc As Long = DateTime.Now.Ticks
                    Dim ret As Single = (DataIn / 1024) * (10000000 / (tc - rateOutTicks))
                    DataIn = 0
                    rateInTicks = tc
                    Return ret
                End Get
            End Property

            Public ReadOnly Property DataRateOut() As Single
                Get
                    Dim tc As Long = DateTime.Now.Ticks
                    Dim ret As Single = (DataOut / 1024) * (10000000 / (tc - rateOutTicks))
                    DataOut = 0
                    rateOutTicks = tc
                    Return ret
                End Get
            End Property

            Private rateInTicks As Long
            Private rateOutTicks As Long
            Private rateIn As Single
            Private rateOut As Single

            Function Listen(ByVal sIP As String, ByVal iPort As Integer) As Boolean

                Dim sock As New System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Tcp)
                Dim EP As System.Net.IPEndPoint
                Dim IP As System.Net.IPAddress

                IP = IPAddress.Parse(sIP)
                EP = New System.Net.IPEndPoint(IP, iPort)
                sock = New System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Tcp)

                Try

                    With sock
                        .Bind(EP)
                        If .IsBound Then
                            .Listen(128)
                            .BeginAccept(New AsyncCallback(AddressOf Accept), sock)
                            Return True
                        Else
                            Return False
                        End If
                    End With

                Catch ex As Exception

                    Return False

                End Try

            End Function

            Function Listen(ByVal IP As IPAddress, ByVal iPort As Integer) As Boolean

                Dim sock As New System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Tcp)
                Dim EP As System.Net.IPEndPoint

                EP = New System.Net.IPEndPoint(IP, iPort)
                sock = New System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Tcp)

                Try

                    With sock
                        .Bind(EP)
                        If .IsBound Then
                            .Listen(128)
                            .BeginAccept(New AsyncCallback(AddressOf Accept), sock)
                            Return True
                        Else
                            Return False
                        End If

                    End With

                Catch ex As Exception

                    Return False

                End Try

            End Function

            Sub SendData(ByVal Data As String, ByVal Ind As Integer)

                Dim tConn As Connection = Nothing

                Try

                    Dim bData() As Byte = ASCII.GetBytes(Data)
                    tConn = ConnectionList.Item(Ind)

                    If Not tConn Is Nothing Then
                        SyncLock tConn.objSync
                            If Not tConn.WSocket Is Nothing Then
                                If tConn.WSocket.Connected Then

                                    tConn.WSocket.Send(bData)
                                    System.Windows.Forms.Application.DoEvents()

                                    RaiseEvent onSend(tConn, Data)

                                End If
                            End If
                        End SyncLock
                    End If

                Catch Ex As Exception

                    RaiseEvent onError("SendData", Ex, tConn)
                    Disconnect(tConn)
                    Exit Sub

                End Try

            End Sub

            Sub SendData(ByVal bData() As Byte, ByVal Ind As Integer)

                Dim tConn As Connection = Nothing

                Try

                    tConn = ConnectionList.Item(Ind)
                    If Not tConn Is Nothing Then
                        SyncLock tConn.objSync
                            If Not tConn.WSocket Is Nothing Then
                                If tConn.WSocket.Connected Then

                                    tConn.WSocket.Send(bData)
                                    System.Windows.Forms.Application.DoEvents()

                                    Dim bytes As Long = bData.Length

                                    DataOut += bytes
                                    TotalDataTransfered += bytes

                                    RaiseEvent onSend(tConn, ASCII.GetString(bData))

                                End If
                            End If
                        End SyncLock
                    End If

                Catch Ex As Exception

                    RaiseEvent onError("SendData", Ex, tConn)
                    Disconnect(tConn)
                    Exit Sub

                End Try

            End Sub

            Sub SendData(ByVal bData() As Byte, ByRef tConn As Connection)

                SendData(bData, tConn.Index)

            End Sub

            Private Sub Accept(ByVal Ar As IAsyncResult)

                Dim acceptSock As System.Net.Sockets.Socket = CType(Ar.AsyncState, System.Net.Sockets.Socket)
                Dim recieveSock As System.Net.Sockets.Socket = acceptSock.EndAccept(Ar)
                Dim tConn As New Connection

                Try

                    tConn.WSocket = recieveSock

                    Dim IP As String = Split(recieveSock.RemoteEndPoint.ToString, ":")(0)
                    tConn.IP = IP

                    tConn = ConnectionList.Add(tConn, IP)

                    ReDim tConn.Buffer(tConn.BufferSize)

                    acceptSock.BeginAccept(New AsyncCallback(AddressOf Accept), acceptSock)

                    recieveSock.BeginReceive(tConn.Buffer, 0, tConn.BufferSize, 0, New AsyncCallback(AddressOf Recieve), tConn)

                Catch ex As Exception

                    RaiseEvent onError("Accept", ex, tConn)

                End Try

                RaiseEvent onAccept(tConn)
            End Sub

            Private Sub Recieve(ByVal Ar As IAsyncResult)

                Dim tConn As Connection = CType(Ar.AsyncState, Connection)

                If Not tConn.WSocket Is Nothing Then
                    If tConn.WSocket.Connected Then

                        Dim Bytes As Integer = Nothing
                        Dim bData() As Byte = Nothing

                        'TODO: IP Ban
                        'If CheckIPBan(Socket.SocketList.GetIP(tConn.Index)) Then
                        'Disconnect(tConn)
                        'Exit Sub
                        'End If

                        Try

                            Bytes = tConn.WSocket.EndReceive(Ar)

                        Catch Ex As SocketException

                            RaiseEvent onError("RecieveData", Ex, tConn)
                            Disconnect(tConn)
                            Exit Sub

                        End Try

                        If Bytes = 0 Then

                            Disconnect(tConn)
                            Exit Sub

                        Else

                            ReDim Preserve bData(Bytes - 1)
                            'ReDim Preserve tConn.Buffer(tConn.BufferSize)

                            Array.Copy(tConn.Buffer, 0, bData, 0, Bytes)

                            Try

                                If Not tConn.WSocket Is Nothing Then
                                    If tConn.WSocket.Connected Then

                                        tConn.WSocket.BeginReceive(tConn.Buffer, 0, tConn.BufferSize, 0, New AsyncCallback(AddressOf Recieve), tConn)

                                        DataIn += Bytes
                                        TotalDataTransfered += Bytes

                                        RaiseEvent onDataArrival(bData, tConn)

                                    End If
                                End If

                            Catch Ex As Exception

                                RaiseEvent onError("RecieveData", Ex, tConn)
                                Disconnect(tConn)
                                Exit Sub

                            End Try

                        End If

                    End If

                End If

                'Try

                'Catch Ex As Exception

                'RaiseEvent onError("RecieveData", Ex, tConn)

                'End Try

            End Sub

            Sub EndConnection(ByVal Ind As Integer)

                Dim tConn As Connection

                tConn = ConnectionList.Item(Ind)

                If Not tConn Is Nothing Then

                    Disconnect(tConn)

                End If

            End Sub

            Public Sub EndAllNonGMConnection()

                ConnectionList.DisconnectAllButGM()

            End Sub

            Private Sub Disconnect(ByVal tConn As Connection)

                Dim sIP As String = ConnectionList.GetIP(tConn.Index)

                If Not tConn.WSocket Is Nothing Then
                    If tConn.WSocket.Connected Then

                        Try

                            tConn.WSocket.Shutdown(SocketShutdown.Both)

                            tConn.WSocket.Close()

                        Catch

                        End Try


                    End If
                End If

                ConnectionList.Remove(tConn.Index)
                RaiseEvent onDisconnect(tConn, sIP)

            End Sub

        End Class

        Public Class cConnectionList

            Private Structure ListType
                Dim Conn As Connection
                Dim IP As String
                Dim IncompleteData() As Byte
            End Structure

            Private Owner As mSocketServer.SocketServer
            Private Lst As New Hashtable

            Public Function Item(ByVal Ind As Integer) As Connection
                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    Return tInfo.Conn
                Else
                    Return Nothing
                End If
            End Function

            Public Function Add(ByVal tConn As Connection, ByVal IP As String) As Connection

                Dim i As Integer
                Dim tInfo As ListType

                For i = 0 To 32766

                    If Lst.Contains(i) = False Then

                        If Lst(i) Is Nothing Then

                            tConn.Index = i
                            tConn.Username = ""
                            tConn.PlayerInfo.STR_NAME = ""
                            tConn.LastRecievedDataAt = DateTime.Now
                            tInfo.Conn = tConn
                            tInfo.IP = IP
                            tInfo.IncompleteData = Nothing
                            Lst.Add(i, tInfo)

                            Return tConn

                        End If

                    End If

                Next i

                Return Nothing

            End Function

            Public Function Count() As Integer

                Dim i As Integer
                Dim c As Integer

                For i = 0 To Lst.Count - 1
                    If Not Lst(i) Is Nothing Then
                        c += 1
                    End If
                Next i

                Return c

            End Function

            Public Sub Remove(ByVal Ind As Integer)

                Lst.Remove(Ind)

            End Sub

            Public Function GetUsername(ByVal Ind As Integer) As String

                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    Return tInfo.Conn.Username
                Else
                    Return ""
                End If

            End Function

            Public Function GetLoginID(ByVal Ind As Integer) As Integer

                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    Return tInfo.Conn.LoginID
                Else
                    Return ""
                End If

            End Function

            Public Function GetCharacterName(ByVal Ind As Integer) As String

                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    Return tInfo.Conn.PlayerInfo.STR_NAME
                Else
                    Return ""
                End If

            End Function

            Public Function GetConnFromCharacterID(ByVal charID As Integer) As Connection

                Dim conn As Connection

                For i As Integer = 0 To Lst.Count - 1
                    If Not Lst(i) Is Nothing Then
                        conn = Lst(i).conn
                        If conn.CharacterID = charID Then
                            Return conn
                        End If
                    End If
                Next

                Return Nothing

            End Function

            Public Function GetConnFromCharacterName(ByVal name As String) As Connection

                Dim conn As Connection

                For i As Integer = 0 To Lst.Count - 1
                    If Not Lst(i) Is Nothing Then
                        conn = Lst(i).conn
                        If conn.PlayerInfo.STR_NAME = name Then
                            Return conn
                        End If
                    End If
                Next

                Return Nothing

            End Function

            Public Function GetConnFromUserName(ByVal name As String) As Connection

                Dim conn As Connection

                For i As Integer = 0 To Lst.Count - 1
                    If Not Lst(i) Is Nothing Then
                        conn = Lst(i).conn
                        If conn.Username = name Then
                            Return conn
                        End If
                    End If
                Next

                Return Nothing

            End Function

            Public Function GetCharacterID(ByVal Ind As Integer) As Integer

                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    Return tInfo.Conn.CharacterID
                Else
                    Return ""
                End If

            End Function

            Public Function GetIP(ByVal Ind As Integer) As String

                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    Return tInfo.IP
                Else
                    Return ""
                End If

            End Function

            Public Function GetIncompleteData(ByVal Ind As Integer) As Byte()

                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    Return tInfo.IncompleteData
                Else
                    Return Nothing
                End If

            End Function

            Public Sub SetIncompleteData(ByVal Ind As Integer, ByVal val As Byte())
                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    tInfo.IncompleteData = val
                    Lst(Ind) = tInfo
                End If

            End Sub

            Public Function GetCrypto(ByVal Ind As Integer) As Object

                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    Return tInfo.Conn.Crypto
                Else
                    Return Nothing
                End If

            End Function

            Public Sub SetCrypto(ByVal Ind As Integer, ByVal val As Object)
                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    tInfo.Conn.Crypto = val
                    Lst(Ind) = tInfo
                End If

            End Sub

            Public Sub SetLogin(ByVal Ind As Integer, ByVal Username As String, ByVal LoginID As Integer)

                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    tInfo.Conn.Username = Username
                    tInfo.Conn.LoginID = LoginID
                    Lst(Ind) = tInfo
                End If

            End Sub

            Public Sub SetToGM(ByVal Ind As Integer)

                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    tInfo.Conn.IsGM = True
                    Lst(Ind) = tInfo
                End If

            End Sub

            Public Sub SetCharacter(ByVal Ind As Integer, ByVal CharName As String, ByVal CharID As Integer, ByVal CharGender As Byte, ByVal CharJob As Byte)

                If Not Lst(Ind) Is Nothing Then
                    Dim tInfo As ListType = Lst(Ind)
                    tInfo.Conn.PlayerInfo.STR_NAME = CharName
                    tInfo.Conn.CharacterID = CharID
                    tInfo.Conn.PlayerInfo.BYTE_GENDER = CharGender
                    tInfo.Conn.PlayerJob = CharJob
                    Lst(Ind) = tInfo
                End If

            End Sub

            Public Sub Cleanup()

                Dim tInfo As ListType

                For i As Integer = 0 To Lst.Count - 1

                    tInfo = Lst(i)
                    If Not tInfo.Conn Is Nothing Then
                        If tInfo.Conn.LastRecievedDataAt.AddSeconds(InactiveTimeoutSecs) < DateTime.Now Then
                            'NO data has been received on this socket within 30 Seconds

                            'Send DisconnectClient.Notify packet to the client in question
                            Dim pkt As Packet
                            pkt.PacketType = GamePacketType.DisconnectClient
                            pkt.PacketOpCode = PacketOpCode.Notify
                            pkt.Username = tInfo.Conn.Username
                            Me.Owner.SendData(MakePacket(pkt, Nothing, Nothing), tInfo.Conn.Index)

                            System.Windows.Forms.Application.DoEvents()

                            'disconnect client with a delay
                            Dim T As Thread = New Thread(AddressOf DoDisconnectCleanup)
                            T.Start(tInfo.Conn)

                        End If
                    End If

                Next

            End Sub

            Private Sub DoDisconnectCleanup(conn As Connection)

                Thread.Sleep(1000)
                Owner.EndConnection(conn.Index)

            End Sub

            Public Sub DisconnectAllButGM()

                Dim tInfo As ListType

                For i As Integer = 0 To Lst.Count - 1

                    tInfo = Lst(i)
                    If Not tInfo.Conn Is Nothing Then
                        If tInfo.Conn.IsGM Then
                            Owner.EndConnection(tInfo.Conn.Index)
                        End If
                    End If

                Next

            End Sub

            Public Sub New(socksvr As mSocketServer.SocketServer)

                Owner = socksvr

            End Sub

        End Class

    End Module

End Namespace